#include "StdAfx.h"

#include "CoreUtil.h"

using namespace System::Runtime::InteropServices;
using namespace DsoFileNET;

void CoreUtil::OpenPropertyStorage(ptr<IPropertySetStorage> %pPropSS, REFFMTID fmtid, bool readOnly, DsoFileNET::FileOpenOptions flags, ptr<IPropertyStorage> %ppPropStg)
{
	if (!pPropSS)
		throw gcnew System::ArgumentNullException("pPropSS");

	bool noCreate = (flags & FileOpenOptions::NoAutoCreate) == FileOpenOptions::NoAutoCreate;
	bool useAnsi = (flags & FileOpenOptions::UseMBCSStringsForNewSets) == FileOpenOptions::UseMBCSStringsForNewSets;

	DWORD mode = STGM_SHARE_EXCLUSIVE;
	mode |= readOnly ? STGM_READ : STGM_READWRITE;

	HRESULT hr = S_OK;
	IPropertyStorage* tmp;

	hr = pPropSS->Open(fmtid, mode, &tmp);

	if (SUCCEEDED(hr))
	{
		ppPropStg.Attach(tmp);
		tmp->Release();
	}
	else if (hr == STG_E_FILENOTFOUND && !readOnly && !noCreate)
	{
		// Try to create, passing the ansi flag for Outlook
		hr = pPropSS->Create(fmtid, NULL, (useAnsi ? PROPSETFLAG_ANSI : 0), mode, &tmp);

		if (SUCCEEDED(hr) && useAnsi && (tmp))
		{
			//VARIANT codePage;
			ppPropStg.Attach(tmp);
			tmp->Release();

			System::Object^ codePage = nullptr;
			PROPSPEC codePageProp;

			codePageProp.ulKind = PRSPEC_PROPID;
			codePageProp.propid = PID_CODEPAGE;

			if (!(ReadProperty(ppPropStg, codePageProp, 0, codePage)))
			{
			//	codePage.vt = VT_I4;
			//	codePage.lVal = GetACP();
			//	WriteProperty(*ppPropStg, codePageProp, 0, &codePage);
			}
		}
	}
}

bool CoreUtil::ReadProperty(ptr<IPropertyStorage> %pPropStg, PROPSPEC spc, short codePage, System::Object^% result)
{
	if (!pPropStg)
		throw gcnew System::ArgumentNullException("pPropStg");

	bool ret = false;

	HRESULT hr = S_OK;
	PROPVARIANT prop;

	PropVariantInit(&prop);

	try
	{
		hr = pPropStg->ReadMultiple(1, &spc, &prop);
	}
	catch (System::Exception^ ex)
	{
	}

	if (SUCCEEDED(hr))
	{
		ret = true;

		switch (prop.vt)
		{
		case VT_I4:
		case VT_UI4:
			result = safe_cast<int>(prop.lVal);
			break;

		case VT_I2:
		case VT_UI2:
			result = safe_cast<short>(prop.iVal);
			break;

		case VT_BSTR:
			result = Marshal::PtrToStringBSTR(System::IntPtr(prop.bstrVal));
			break;

		case VT_LPWSTR:
			result = Marshal::PtrToStringUni(System::IntPtr(prop.pwszVal));
			break;

		case VT_LPSTR:
			result = Marshal::PtrToStringAnsi(System::IntPtr(prop.pszVal));
			break;

		case VT_BOOL:
			result = safe_cast<bool>(prop.boolVal == VARIANT_TRUE);
			break;

		case VT_R4:
			result = safe_cast<float>(prop.fltVal);
			break;

		case VT_R8:
			result = safe_cast<double>(prop.dblVal);
			break;

		case VT_FILETIME:
			{
				// TODO: Maybe import the old logic, for now we're just going to get a DateTime from the FILETIME
				unsigned __int64 ft = 0;
				ft = prop.filetime.dwHighDateTime;
				ft <<= 32;
				ft += prop.filetime.dwLowDateTime;

				result = System::DateTime::FromFileTime(safe_cast<__int64>(ft));
			}
			break;

		case VT_CF:
			// TODO: OleStdPic
			// ConvertCFPicToOleStdPic
			//break;

		case VT_BLOB:
			// TODO: BLOB
			// ConvertBlobToVarVector
			//break;

		default:
			// TODO: Throw an exception
			//hr = STG_E_INVALIDPARAMETER;
			ret = false;
			break;
		}
	}

	PropVariantClear(&prop);

	return ret;
}

generic <typename T>
void CoreUtil::LoadPropertySetList(ptr<IPropertyStorage> %pPropStg, short %codePage, Dictionary<T, CustomProperty<T>^>^ props)
{
	HRESULT hr = S_OK;
	ULONG fetched = 0;
	STATPROPSTG sps = {0};
	PROPSPEC spc = {0};

	Object^ tmp = nullptr;

	codePage = 0;
	spc.ulKind = PRSPEC_PROPID;
	spc.propid = PID_CODEPAGE;
	// Get the code page
	if (ReadProperty(pPropStg, spc, codePage, tmp))
	{
		codePage = safe_cast<short>(tmp);
	}

	IEnumSTATPROPSTG* pEnum = NULL;

	hr = pPropStg->Enum(&pEnum);
	if (SUCCEEDED(hr) && (pEnum))
	{
		while (pEnum->Next(1, &sps, &fetched) == S_OK)
		{
			//if readable?
			spc.ulKind = PRSPEC_PROPID;
			spc.propid = sps.propid;

			tmp = nullptr;
			if (ReadProperty(pPropStg, spc, codePage, tmp))
			{
				String^ name = Marshal::PtrToStringUni(IntPtr(sps.lpwstrName));
				T id = safe_cast<T>((int)sps.propid);
				CustomProperty<T>^ prop = gcnew CustomProperty<T>(name, id, tmp, false);
				props->Add(id, prop);
			}

			if (sps.lpwstrName)
				CoTaskMemFree(sps.lpwstrName);
		}
		pEnum->Release();
	}
}

/*
void CoreUtil::ConvertBlobToVarVector(PROPVARIANT *pVarBlob, VARIANT *pVarByteArray)
{
    HRESULT hr = S_FALSE;
    SAFEARRAY* psa;
    DWORD dwSize;

    if ((pVarBlob == NULL) || (pVarBlob->vt != VT_BLOB) || (pVarByteArray == NULL))
        return E_UNEXPECTED;

 // Identify the size of the blob...
    dwSize = pVarBlob->blob.cbSize;
    if ((dwSize) && (dwSize < 0x800000))
    {
     // Create a vector array the size of the blob...
        psa = SafeArrayCreateVector(VT_UI1, 0, dwSize);
        if (psa != NULL)
        {
         // Copy the data over to the vector...
            BYTE *pbyte = NULL;
            hr = SafeArrayAccessData(psa, (void**)&pbyte);
            if (SUCCEEDED(hr))
            {
                SEH_TRY
                memcpy(pbyte, (BYTE*)(pVarBlob->blob.pBlobData), dwSize);
                SEH_EXCEPT(hr)
                SafeArrayUnaccessData(psa);
            }
        }

        if ((psa) && SUCCEEDED(hr) && (pVarByteArray))
        {
            pVarByteArray->vt = (VT_ARRAY | VT_UI1);
            pVarByteArray->parray = psa;
        }
        else if (psa) SafeArrayDestroy(psa);
    }

//    return hr;
}

void CoreUtil::ConvertCFPicToOleStdPic(PROPVARIANT *pVarCF, VARIANT *pVarStdPicture)
{
    HRESULT hr = S_FALSE;
    CLIPDATA* pclp;

    if ((pVarCF == NULL) || (pVarCF->vt != VT_CF) || (pVarStdPicture == NULL))
        return E_UNEXPECTED;

    SEH_TRY
 // Convert the data to OLE StdPicture object. We handle the process for
 // thumbnails saved as WMF (Word/Execl) and DIB (PhotoShop)...
	pclp = pVarCF->pclipdata;
	if (((DWORD)*pclp->pClipData) == CF_METAFILEPICT)
	{
		DWORD cbDataSize, cbHeaderSize;
		HMETAFILE hwmf;

		cbHeaderSize = (sizeof(DWORD) + (4 * sizeof(WORD)));
		cbDataSize = pclp->cbSize - cbHeaderSize;

		hwmf = SetMetaFileBitsEx(cbDataSize, (BYTE*)(pclp->pClipData + cbHeaderSize));
		if (NULL != hwmf)
		{
			PICTDESC pds;
			IDispatch* pdisp;

			pds.cbSizeofstruct = sizeof(PICTDESC);
			pds.picType = PICTYPE_METAFILE;
			pds.wmf.hmeta = hwmf;
			pds.wmf.xExt = 3000;
			pds.wmf.yExt = 4000;

			hr = OleCreatePictureIndirect(&pds, IID_IDispatch, TRUE, (void**)&pdisp);
			if (SUCCEEDED(hr))
			{
				pVarStdPicture->vt = VT_DISPATCH;
				pVarStdPicture->pdispVal = pdisp;
			}
		}
	}
	else if ((((DWORD)*pclp->pClipData) == CF_DIB) || (((DWORD)*pclp->pClipData) == CF_BITMAP))
	{
		DWORD dwSize = pclp->cbSize;

		PICTDESC pds;
		HANDLE hBitmap;
		BITMAP bm;
		BITMAPINFOHEADER* pbmh;
		IDispatch* pdisp;

		pbmh = (BITMAPINFOHEADER*)((pclp->pClipData) + 4);

		bm.bmType = 0;
		bm.bmWidth = pbmh->biWidth;
		bm.bmHeight = pbmh->biHeight;
		bm.bmPlanes = pbmh->biPlanes;
		bm.bmBitsPixel = pbmh->biBitCount;
		bm.bmWidthBytes = (pbmh->biSizeImage / bm.bmHeight);

		bm.bmBits = (((pclp->pClipData) + 4) + pbmh->biSize);

		hBitmap = CreateBitmapIndirect(&bm);
		if (hBitmap)
		{
			pds.cbSizeofstruct = sizeof(PICTDESC);
			pds.picType = PICTYPE_BITMAP;
			pds.bmp.hpal = NULL;
			pds.bmp.hbitmap = (HBITMAP)hBitmap;

			hr = OleCreatePictureIndirect(&pds, IID_IDispatch, TRUE, (void**)&pdisp);
			if (SUCCEEDED(hr))
			{
				pVarStdPicture->vt = VT_DISPATCH;
				pVarStdPicture->pdispVal = pdisp;
			}
		}
	}

    SEH_EXCEPT_NULL
//    return hr;
}

void CoreUtil::WriteProperty(IPropertyStorage* pPropStg, PROPSPEC spc, WORD wCodePage, VARIANT* pvtValue)
{
	HRESULT        hr;
	PROPVARIANT    vtProperty;
	STATPROPSETSTG statstg;
	BOOL           fUseANSI = FALSE;

 // Check the storage and discover whether it is ANSI only...
	SEH_TRY

	if (SUCCEEDED(pPropStg->Stat(&statstg)))
		fUseANSI = ((statstg.grfFlags & PROPSETFLAG_ANSI) == PROPSETFLAG_ANSI);

	SEH_EXCEPT(hr)

 // We only support certain Variant types...
	switch (pvtValue->vt)
	{
	case VT_I4:
	case VT_UI4:
		vtProperty.vt = VT_I4; vtProperty.lVal = pvtValue->lVal;
		break;

	case VT_I2:
	case VT_UI2:
		vtProperty.vt = VT_I2; vtProperty.iVal = pvtValue->iVal;
		break;

	case VT_BOOL:
		vtProperty.vt = VT_BOOL; vtProperty.boolVal = pvtValue->boolVal;
		break;

	case VT_BSTR:
		if (fUseANSI) // When using ANSI propset, convert to local code page...
		{
			vtProperty.vt = VT_LPSTR;
			vtProperty.pszVal = DsoConvertToMBCS(pvtValue->bstrVal, wCodePage);
		}
		else // Otherwise we save the (Unicode) BSTR...
		{
		  /////////////////////////////////////////////////////////////////////
		  // BUG (6/30/01): Changed from saving directly as BSTR to LPWSTR since
		  // Win2K SP2 introduced bug with VT_BSTR types and does not show them
		  // correctly in the UI. We just copy string before handing to OLE.
			vtProperty.vt = VT_LPWSTR;
			vtProperty.pwszVal = DsoConvertToCoTaskMemStr(pvtValue->bstrVal);
		}
		break;

	case VT_DATE: // Date/time values should always be saved as UTC...
		{
			FILETIME utc = {0,0};
			FILETIME lft;
			SYSTEMTIME lst;
			if ((0 != pvtValue->date) && 
				(VariantTimeToSystemTime(pvtValue->date, &lst)) &&
				(SystemTimeToFileTime(&lst, &lft)))
			{
				if (!LocalFileTimeToFileTime(&lft, &utc))
					utc = lft;
			}
			vtProperty.vt = VT_FILETIME;
			vtProperty.filetime = utc;
		}
		break;

	case VT_R4:
		vtProperty.vt = VT_R4;
		vtProperty.fltVal = pvtValue->fltVal;
		break;

	case VT_R8:
		vtProperty.vt = VT_R8;
		vtProperty.dblVal = pvtValue->dblVal;
		break;

	default:
		return E_INVALIDARG; //unsupportted type...
	}

 // Do the Write operation to the given IPropertySet...
	SEH_TRY
    hr = pPropStg->WriteMultiple(1, &spc, &vtProperty, ((spc.ulKind == PRSPEC_LPWSTR) ? 0x2001 : NULL));
    SEH_EXCEPT(hr)

	PropVariantClear(&vtProperty);
//	return hr;
}

bool CoreUtil::VarTypeReadable(VARTYPE vt)
{
    BOOL fReadable = FALSE;
    switch (vt)
    {
	    case VT_I4:
	    case VT_UI4:
	    case VT_I2:
	    case VT_UI2:
	    case VT_BSTR:
	    case VT_LPWSTR:
	    case VT_LPSTR:
	    case VT_FILETIME:
	    case VT_BOOL:
	    case VT_R4:
	    case VT_R8:
        case VT_CF:
        case VT_BLOB: fReadable = TRUE;  break;
    }
    return fReadable;
}

void CoreUtil::SavePropertySetList(IPropertyStorage *pPropStg, WORD wCodePage, CDsoDocProperty* plist, ULONG *pcSavedItems)
{
    HRESULT hr = S_FALSE;
    CDsoDocProperty* pitem = plist;
    VARIANT *pvt;
    BSTR bstrName = NULL;
    ULONG cItemsChanged = 0;
    PROPSPEC spc;

    if ((pPropStg == NULL) || (plist == NULL))
        return E_UNEXPECTED;

 // Loop through each item in the list...
    while (pitem)
    {
     // If the item is removed, remove it from the document...
        if (pitem->IsRemoved())
        {
         // We only need to remove it if it already exists. If
         // this is an item wehen added then deleted before save,
         // we don't need to do anything...
            if (pitem->IsNewItem() == FALSE)
            {                
             // Determine if item is known by name or by id...
                pitem->get_Name(&bstrName);
                if (bstrName)
                {
                    spc.ulKind = PRSPEC_LPWSTR;
                    spc.lpwstr = bstrName;
                }
                else
                {
                    spc.ulKind = PRSPEC_PROPID;
                    spc.propid = pitem->GetID();

                    if (spc.propid == 0)
						{ ODS(" ** Bad Propid!\n"); hr = E_UNEXPECTED; break; }
                }
            
             // Now remove the item...
                hr = pPropStg->DeleteMultiple(1, &spc);

             // Break out if error occurred...
                if (FAILED(hr)) { TRACE1(" ** DeleteMultiple FAILED hr=0x%X\n", hr); break;}

             // Since we changed an item in the file, we need 
             // to increment the count...
                pitem->OnRemoveComplete();
                ++cItemsChanged;
            }
        }
        else if (pitem->IsDirty())
        {
     // If the item is dirty, try to save it now...
            pvt = pitem->GetDataPtr();
            if ((pvt) && (pvt->vt != VT_EMPTY))
            {
             // Determine if we should save by name or by id...
                pitem->get_Name(&bstrName);
                if (bstrName)
                {
                    spc.ulKind = PRSPEC_LPWSTR;
                    spc.lpwstr = bstrName;
                }
                else
                {
                    spc.ulKind = PRSPEC_PROPID;
                    spc.propid = pitem->GetID();

                    if (spc.propid == 0)
						{ ODS(" ** Bad Propid!\n"); hr = E_UNEXPECTED; break; }
                }

             // Write the property to the property set...
                hr = DsoWriteProperty(pPropStg, spc, wCodePage, pvt);

                FREE_BSTR(bstrName);

             // Break out if error occurred...
                if (FAILED(hr)){ TRACE1(" ** DsoWriteProperty FAILED hr=0x%X\n", hr); break;}

             // Notify object that it was saved, and bump up
             // the modified item count...
                pitem->OnSaveComplete();
                ++cItemsChanged;
            }
        }

        pitem = pitem->GetNextProperty();
    }

    if (pcSavedItems)
        *pcSavedItems = cItemsChanged;

//    return hr;
}

void CoreUtil::GetTypeInfoEx(REFGUID rlibid, LCID lcid, WORD wVerMaj, WORD wVerMin, HMODULE hResource, REFGUID rguid, ITypeInfo** ppti)
{
	HRESULT     hr;
	ITypeLib*   ptlib;

	CHECK_NULL_RETURN(ppti, E_POINTER);
    *ppti = NULL;

 // Try to pull information from the registry...
    hr = LoadRegTypeLib(rlibid, wVerMaj, wVerMin, lcid, &ptlib);

 // If that failed, and we have a resource module to load from,
 // try loading it from the module...
    if (FAILED(hr) && (hResource))
    {
		LPWSTR pwszPath;
        if (FGetModuleFileName(hResource, &pwszPath))
        {
         // Now, load the type library from module resource file...
			hr = LoadTypeLib(pwszPath, &ptlib);

		 // Register library to make things easier next time...
			if (SUCCEEDED(hr))
                RegisterTypeLib(ptlib, pwszPath, NULL);

			DsoMemFree(pwszPath);
		}
    }

 // We have the typelib. Now get the requested typeinfo...
	if (SUCCEEDED(hr))
        hr = ptlib->GetTypeInfoOfGuid(rguid, ppti);

 // Release the type library interface.
    RELEASE_INTERFACE(ptlib);
//	return hr;
}
*/